package com.dongnam.quanly.web.privilege;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;
import javax.faces.bean.ViewScoped;

import org.hibernate.Session;
import org.hibernate.exception.ConstraintViolationException;
import org.primefaces.context.RequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.dongnam.quanly.common.ActionCategory;
import com.dongnam.quanly.common.ActionType;
import com.dongnam.quanly.common.ObjectType;
import com.dongnam.quanly.common.ScreenId;
import com.dongnam.quanly.common.controller.UserGroupController;
import com.dongnam.quanly.common.controller.UserRoleController;
import com.dongnam.quanly.common.dao.UserGroupDao;
import com.dongnam.quanly.common.exceptions.DatabaseException;
import com.dongnam.quanly.common.model.UserGroup;
import com.dongnam.quanly.common.model.UserRole;
import com.dongnam.quanly.common.persistence.HibernateUtil;
import com.dongnam.quanly.web.DiffComparator;
import com.dongnam.quanly.web.JSFUtility;
import com.dongnam.quanly.web.UserBean;
import com.dongnam.quanly.web.audit.AuditLogger;
import com.dongnam.quanly.web.util.LocaleMessages;
import com.dongnam.quanly.web.util.MessageUtil;

@ManagedBean(name = "UserGroupsBean")
@ViewScoped
public class UserGroupsBean implements Serializable {

	private static final long serialVersionUID = 1L;
	private static final Logger logger = LoggerFactory.getLogger(UserGroupsBean.class);

	@ManagedProperty(value = "#{UserBean}")
	private UserBean userBean;

	private List<UserGroup> userGroups;
	private List<UserRole> selectedRoles; // roles current group has

	private List<UserRole> notSelectedRoles; // roles current group does not
												// have
	private Map<String, UserRole> notSelectedRolesStringMap; // string to
																// userrole
																// mapping of
																// roles current
																// group does
																// not have
	private List<String> notSelectedRolesNames; // names of roles current group
												// does not have

	private String selectedRoleName;
	private UserGroup selectedGroup;
	private UserRole selectedRole;
	private String selectedGroupId;
	private UserGroup prev;

	private boolean createNew;

	public UserGroupsBean() {
		init();
	}

	private void init() {
		setUserGroups(new UserGroupController().findAll());
	}

	// actions //

	public void selectGroup(UserGroup group) {
		selectedGroup = group;
		logger.debug("selectedGroup: {}", selectedGroup);
	}

	public void addGroup() {
		logger.debug("addGroup");
		selectedGroup = new UserGroup();
		logger.debug("selectedGroup: {}", selectedGroup);
	}

	public void deleteGroup() {
		logger.debug("selectedGroup: {}", selectedGroup);
		if (selectedGroup != null && selectedGroup.getGroupId().equals("Superuser")) {
			// NOT ALLOWED to delete super user group
			addErrorMsg("general_delete_failed", LocaleMessages.getString("privilege_groupDeleteSuperGroup"),
					new Date());
			AuditLogger.logAuditTrail(ScreenId.PRIVILEGE_USER, ActionCategory.PRIVILEGE, ActionType.DELETE, false,
					ObjectType.UserGroup, selectedGroup.getGroupId(),
					LocaleMessages.getString("privilege_groupDeleteSuperGroup"), null, null);
			refreshGroups();
			return;
		} else {
			// delete a group will cause deleting all user in the group
			boolean isTransacted = HibernateUtil.getCurrentSession().getTransaction().isActive();
			Session session = isTransacted ? HibernateUtil.getCurrentSession() : HibernateUtil.startCurrentSession();

			try {
				// new UserGroupController().makeTransient(selectedGroup);
				new UserGroupDao().makeTransient(selectedGroup);
				AuditLogger.persistAuditTrail(ScreenId.PRIVILEGE_USER, ActionCategory.PRIVILEGE, ActionType.DELETE,
						true, ObjectType.UserGroup, selectedGroup.getGroupId(), null, null, null);
				if (!isTransacted) {
					session.getTransaction().commit();
				}
			} catch (Exception e) {
				if (!isTransacted && session.isOpen() && !session.getTransaction().wasCommitted())
					session.getTransaction().rollback();
				MessageUtil.logDBException("Delete group", e);
				addErrorMsg("general_delete_failed", MessageUtil.getDescriptionOfDBException(e), new Date());
				AuditLogger.logAuditTrail(ScreenId.PRIVILEGE_USER, ActionCategory.PRIVILEGE, ActionType.DELETE, false,
						ObjectType.UserGroup, selectedGroup == null ? "" : selectedGroup.getGroupId(),
						MessageUtil.getDescriptionOfDBException(e), null, null);
				refreshGroups();
				return;
			}
			addInfoMsg("general_delete_success", selectedGroup.getGroupId());
			selectedGroup = null;
			selectedRoles = null;
			refreshGroups();

		}
	}

	public void saveGroup() {
		RequestContext context = RequestContext.getCurrentInstance();
		if (createNew && failsValidation(selectedGroup)) {
			context.addCallbackParam("opSuccess", false);
			selectedGroup = new UserGroup();
			selectedRoles = null;
			return;
		}
		// store the previous group in order to log changes
		if (!createNew)
			try {
				prev = new UserGroupController().findById(selectedGroup.getGroupId(), false);
			} catch (DatabaseException e) {
				addErrorMsg("general_add_edit_fail", MessageUtil.getDescriptionOfDBException(e), new Date());
				AuditLogger.logAuditTrail(ScreenId.PRIVILEGE_USER, ActionCategory.PRIVILEGE,
						createNew ? ActionType.CREATE : ActionType.UPDATE, false, ObjectType.UserGroup,
						createNew ? null : selectedGroup.getGroupId(), MessageUtil.getDescriptionOfDBException(e),
						null, null);
				MessageUtil.logDBException("Save group", e);
				refreshGroups();
				if (createNew)
					selectedGroup = new UserGroup();
				selectedRoles = null;
				context.addCallbackParam("opSuccess", false);
				return;
			}

		selectedGroup.setLastUpdatedBy(userBean.getUser().getUserId());
		boolean isTransacted = HibernateUtil.getCurrentSession().getTransaction().isActive();
		Session session = isTransacted ? HibernateUtil.getCurrentSession() : HibernateUtil.startCurrentSession();

		try {
			new UserGroupDao().makePersistent(selectedGroup);
			String[] differences = { "", "" };
			if (!createNew) {
				DiffComparator diff = new DiffComparator();
				differences = diff.compare(ObjectType.UserGroup, prev, selectedGroup);
			}
			AuditLogger.persistAuditTrail(ScreenId.PRIVILEGE_USER, ActionCategory.PRIVILEGE,
					createNew ? ActionType.CREATE : ActionType.UPDATE, true, ObjectType.UserGroup,
					selectedGroup.getGroupId(), null, differences[0], differences[1]);
			if (!isTransacted) {
				session.getTransaction().commit();
			}
		} catch (ConstraintViolationException e) {
			if (!isTransacted && session.isOpen() && !session.getTransaction().wasCommitted())
				session.getTransaction().rollback();
			String message = LocaleMessages.getString("privilege_groupnameexist", selectedGroup.getGroupName());
			JSFUtility.setJSFFacesMessage(FacesMessage.SEVERITY_ERROR, message, message);
			AuditLogger.logAuditTrail(ScreenId.PRIVILEGE_USER, ActionCategory.PRIVILEGE, createNew ? ActionType.CREATE
					: ActionType.UPDATE, false, ObjectType.UserGroup, createNew ? null : selectedGroup.getGroupId(),
					message, null, null);
			if (!createNew)
				refreshGroups();
			prev = null;
			if (createNew)
				selectedGroup = new UserGroup();
			selectedRoles = null;
			context.addCallbackParam("opSuccess", false);
			return;
		} catch (Exception e1) {
			if (!isTransacted && session.isOpen() && !session.getTransaction().wasCommitted())
				session.getTransaction().rollback();
			DatabaseException e = new DatabaseException(e1);
			addErrorMsg("general_add_edit_fail", MessageUtil.getDescriptionOfDBException(e), new Date());
			AuditLogger.logAuditTrail(ScreenId.PRIVILEGE_USER, ActionCategory.PRIVILEGE, createNew ? ActionType.CREATE
					: ActionType.UPDATE, false, ObjectType.UserGroup, createNew ? null : selectedGroup.getGroupId(),
					MessageUtil.getDescriptionOfDBException(e), null, null);
			MessageUtil.logDBException("saveGroup", e);
			refreshGroups();
			prev = null;
			if (createNew)
				selectedGroup = new UserGroup();
			selectedRoles = null;
			context.addCallbackParam("opSuccess", false);
			return;
		}
		addInfoMsg("general_add_edit_success", selectedGroup.getGroupId());
		refreshGroups();
		prev = null;
		selectedGroup = null;
		selectedRoles = null;
		context.addCallbackParam("opSuccess", true);
	}

	public void selectRole(UserRole role) {
		selectedRole = role;
		setNotSelectedRoles(new UserRoleController().findAll());
	}

	public void addRoleToGroup() {
		if (selectedGroup == null)
			return;
		selectedRole = getNotSelectedRolesStringMap().get(selectedRoleName);
		boolean isTransacted = HibernateUtil.getCurrentSession().getTransaction().isActive();
		Session session = isTransacted ? HibernateUtil.getCurrentSession() : HibernateUtil.startCurrentSession();

		try {
			// logger.debug("Role to added to group: {} => {}",selectedRole,
			// selectedGroup);
			selectedGroup.addUserRole(selectedRole);
			new UserGroupDao().makePersistent(selectedGroup);
			AuditLogger.persistAuditTrail(ScreenId.PRIVILEGE_USER, ActionCategory.PRIVILEGE, ActionType.UPDATE, true,
					ObjectType.UserGroup, selectedGroup.getGroupId(), null, "",
					"UserRole(id = " + selectedRole.getRoleId() + ") Added");
			if (!isTransacted) {
				session.getTransaction().commit();
			}
		} catch (Throwable e) {
			if (!isTransacted && session.isOpen() && !session.getTransaction().wasCommitted())
				session.getTransaction().rollback();
			addErrorMsg("general_add_edit_fail", MessageUtil.getDescriptionOfDBException(new Exception(e)), new Date());
			AuditLogger.logAuditTrail(ScreenId.PRIVILEGE_USER, ActionCategory.PRIVILEGE, ActionType.UPDATE, false,
					ObjectType.UserGroup, selectedGroup.getGroupId(),
					MessageUtil.getDescriptionOfDBException(new Exception(e)), null, null);
			MessageUtil.logDBException("Add role to group", new Exception(e));
			refreshGroups();
			refreshRoles();
			return;
		}
		String message = LocaleMessages.getString("privilege_groupAddRole", selectedRole.getRoleId(),
				selectedGroup.getGroupId());
		JSFUtility.setJSFFacesMessage(FacesMessage.SEVERITY_INFO, message, message);
		refreshGroups();
		refreshRoles();
	}

	public void deleteRoleFromGroup() {

		selectedGroup.removeUserRole(selectedRole);
		// selectedRole.removeUserGroup(selectedGroup);
		boolean isTransacted = HibernateUtil.getCurrentSession().getTransaction().isActive();
		Session session = isTransacted ? HibernateUtil.getCurrentSession() : HibernateUtil.startCurrentSession();

		try {
			// new UserGroupController().makePersistent(selectedGroup);
			new UserGroupDao().makePersistent(selectedGroup);
			AuditLogger.persistAuditTrail(ScreenId.PRIVILEGE_USER, ActionCategory.PRIVILEGE, ActionType.DELETE, true,
					ObjectType.UserGroup, selectedGroup.getGroupId(), null, "",
					"UserRole(id = " + selectedRole.getRoleId() + ") removed");
			if (!isTransacted) {
				session.getTransaction().commit();
			}
		} catch (Exception e) {
			if (!isTransacted && session.isOpen() && !session.getTransaction().wasCommitted())
				session.getTransaction().rollback();
			MessageUtil.logDBException("Delete role from group", e);
			addErrorMsg("general_delete_failed", MessageUtil.getDescriptionOfDBException(e), new Date());
			AuditLogger.logAuditTrail(ScreenId.PRIVILEGE_USER, ActionCategory.PRIVILEGE, ActionType.UPDATE, false,
					ObjectType.UserGroup, selectedGroup.getGroupId(), MessageUtil.getDescriptionOfDBException(e), null,
					null);
			refreshGroups();
			refreshRoles();
			return;
		}
		String message = LocaleMessages.getString("privilege_groupDeleteRole", selectedRole.getRoleId(),
				selectedGroup.getGroupId());
		JSFUtility.setJSFFacesMessage(FacesMessage.SEVERITY_INFO, message, message);
		refreshGroups();
		refreshRoles();
	}

	public void refreshGroups() {
		setUserGroups(new UserGroupController().findAll());
		setNotSelectedRoles(new UserRoleController().findAll());
		if (selectedGroup != null && selectedGroup.getGroupId() != null && !selectedGroup.getGroupId().isEmpty())
			setSelectedGroup(new UserGroupController().findById(selectedGroup.getGroupId(), false));
	}

	public void refreshRoles() {
		ArrayList<UserRole> temp = null;
		boolean isTransacted = HibernateUtil.getCurrentSession().getTransaction().isActive();
		Session session = isTransacted ? HibernateUtil.getCurrentSession() : HibernateUtil.startCurrentSession();

		try {
			new UserGroupDao().makePersistent(selectedGroup);
			temp = new ArrayList<UserRole>(selectedGroup.getUserRoles());
			if (!isTransacted) {
				session.getTransaction().commit();
			}
		} catch (Throwable e) {
			if (!isTransacted && session.isOpen() && !session.getTransaction().wasCommitted())
				session.getTransaction().rollback();
			// logger.debug("selectedRoles: {}, temp: {}", selectedRoles, temp);
			// e.printStackTrace();
			// addErrorMsg("error_selecting_roles");
			return;
		}
		selectedRoles = temp;
	}

	private String addInfoMsg(String msgKey, String id) {
		String message = LocaleMessages.getString(msgKey, id);
		JSFUtility.setJSFFacesMessage(FacesMessage.SEVERITY_INFO, message, message);
		return message;
	}

	private String addErrorMsg(String msgKey, String e, Date date) {
		String message = LocaleMessages.getString(msgKey, e, date);
		JSFUtility.setJSFFacesMessage(FacesMessage.SEVERITY_ERROR, message, message);
		return message;
	}

	// validation //

	public boolean failsValidation(UserGroup group) {
		return groupIdExistsAlready(group);
	}

	public boolean groupIdExistsAlready(UserGroup newGroup) {
		for (UserGroup group : userGroups) {
			if (group.getGroupId().equals(newGroup.getGroupId()) && group != newGroup) {
				String message = LocaleMessages.getString("privilege_groupExistsAlready", group.getGroupId());
				JSFUtility.setJSFFacesMessage(FacesMessage.SEVERITY_ERROR, message, message);
				AuditLogger.logAuditTrail(ScreenId.PRIVILEGE_USER, ActionCategory.PRIVILEGE, ActionType.CREATE, false,
						ObjectType.UserGroup, selectedGroup.getGroupId(), message, null, null);
				return true; // groupid exists already, bad
			}
		}
		return false; // groupid doesnt exist yet, ok
	}

	// getters setters //

	public List<UserRole> getSelectedRoles() {
		boolean isTransacted = HibernateUtil.getCurrentSession().getTransaction().isActive();
		Session session = isTransacted ? HibernateUtil.getCurrentSession() : HibernateUtil.startCurrentSession();

		try {
			new UserGroupDao().makePersistent(selectedGroup);
			selectedRoles = new ArrayList<UserRole>(selectedGroup.getUserRoles());
			if (!isTransacted) {
				session.getTransaction().commit();
			}
		} catch (Throwable e) {
			if (!isTransacted && session.isOpen() && !session.getTransaction().wasCommitted())
				session.getTransaction().rollback();
			// addErrorMsg("error_selecting_roles");
			return selectedRoles;
		}
		return selectedRoles;
	}

	public void setSelectedRoles(List<UserRole> selectedRoles) {
		this.selectedRoles = selectedRoles;
	}

	public List<UserRole> getNotSelectedRoles() {
		if (selectedGroup != null) {
			List<UserRole> roleList = new UserRoleController().findAll();
			if (selectedRoles != null)
				roleList.removeAll(getSelectedRoles());
			return notSelectedRoles = roleList;
		}
		return notSelectedRoles;
	}

	public void setNotSelectedRoles(List<UserRole> notSelectedRoles) {
		this.notSelectedRoles = notSelectedRoles;
	}

	public List<String> getNotSelectedRolesNames() {
		if (selectedGroup != null) {
			notSelectedRolesNames = new ArrayList<String>();
			for (UserRole role : getNotSelectedRoles()) {
				notSelectedRolesNames.add(role.getRoleId());
			}
		}
		return notSelectedRolesNames;
	}

	public Map<String, UserRole> getNotSelectedRolesStringMap() {
		if (selectedGroup != null) {
			notSelectedRolesStringMap = new HashMap<String, UserRole>();
			for (UserRole role : getNotSelectedRoles()) {
				notSelectedRolesStringMap.put(role.getRoleId(), role);
			}
		}
		return notSelectedRolesStringMap;
	}

	public List<UserGroup> getUserGroups() {
		if (userGroups == null) {
			return userGroups = new UserGroupController().findAll();
		}
		return userGroups;
	}

	public void setUserGroups(List<UserGroup> userGroups) {
		this.userGroups = userGroups;
	}

	public UserGroup getSelectedGroup() {
		return selectedGroup;
	}

	public void setSelectedGroup(UserGroup selectedGroup) {
		this.selectedGroup = selectedGroup;
	}

	public UserRole getSelectedRole() {
		return selectedRole;
	}

	public void setSelectedRole(UserRole selectedRole) {
		this.selectedRole = selectedRole;
	}

	public String getSelectedRoleName() {
		if (selectedRole != null)
			// return selectedRoleName;
			return selectedRole.getRoleName();
		else
			return selectedRoleName;
	}

	public void setSelectedRoleName(String selectedRoleName) {
		this.selectedRoleName = selectedRoleName;
	}

	public void setUserBean(UserBean userBean) {
		this.userBean = userBean;
	}

	public boolean isCreateNew() {
		return createNew;
	}

	public void setCreateNew(boolean createNew) {
		this.createNew = createNew;
	}

	public void setSelectedGroupId(String selectedGroupId) {
		// logger.debug("selectedgroupid set");
		this.selectedGroupId = selectedGroupId;
		if (userGroups == null)
			init();
		for (UserGroup group : userGroups) {
			if (group.getGroupId().equals(selectedGroupId)) {
				selectGroup(group);
				logger.debug("Group {} selected", group);
			}
		}
	}

	public String getSelectedGroupId() {
		return selectedGroupId;
	}
}
